/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


//TIP 要<b>运行</b>代码，请按 <shortcut actionId="Run"/> 或
// 点击间距中的 <icon src="AllIcons.Actions.Execute"/> 图标。
/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.util.ArrayList;
import java.util.List;

class Raytrace {
    public static int checkNumber = 0;

    public static boolean debug = false;

    public static void debugLog(String msg) {
        if (debug) {
            System.out.println(msg);
        }
    }

    public static final int MAX_COLOR_VALUE = 255;
    public static final int WEIGHT_RED = 77;
    public static final int WEIGHT_GREEN = 150;
    public static final int WEIGHT_BLUE = 29;

    public static final double NUMBER_TWO = 2.0;
    public static final double NUMBER_THREE = 3;
    public static final double NUMBER_FIVE = 5;
    public static final double NUMBER_EIGHT = 8;
    public static final double NUMBER_TEN = 10.0;
    public static final double NUMBER_ONE_POINT_TWO = 1.2;
    public static final double NUMBER_ONE_POINT_FIVE = 1.5;
    public static final double NUMBER_POINT_ONE = 0.1;
    public static final double NUMBER_POINT_TWO = 0.2;
    public static final double NUMBER_POINT_TWO_FIVE = 0.25;
    public static final double NUMBER_POINT_THREE = 0.3;
    public static final double NUMBER_POINT_FOUR = 0.4;
    public static final double NUMBER_POINT_FIVE = 0.5;
    public static final double NUMBER_POINT_SEVEN = 0.7;
    public static final double NUMBER_POINT_EIGHT = 0.8;
    public static final double NUMBER_POINT_NINE = 0.9;
    public final double NUMBER_ONE_THOUSAND = 1000;
    public static final double NUMBER_ONE_HUNDRED = 100;

    public static final int CHECK_NUMBER = 2321;
    public static final int MAX_DISTANCE = 2000;
    public static final int DEFAULT_CANVAS_HEIGHT = 100;
    public static final int DEFAULT_CANVAS_WIDTH = 100;
    public static final double DEFAULT_VECTOR_Z = -15;

    public static final int MAX_RUN_TIMES = 20;

    // Defined class Color
    public static class Color {
        public double red = 0.0;
        public double green = 0.0;
        public double blue = 0.0;

        public Color(Double r, Double g, Double b) {
            this.red = (r != null) ? r : 0.0;
            this.green = (g != null) ? g : 0.0;
            this.blue = (b != null) ? b : 0.0;
        }

        public static Color add(Color c1, Color c2) {
            Color result = new Color(0.0, 0.0, 0.0);

            result.red = c1.red + c2.red;
            result.green = c1.green + c2.green;
            result.blue = c1.blue + c2.blue;

            return result;
        }

        public static Color addScalar(Color c1, double s) {
            Color result = new Color(0.0, 0.0, 0.0);

            result.red = c1.red + s;
            result.green = c1.green + s;
            result.blue = c1.blue + s;

            result.limit();

            return result;
        }

        public Color subtract(Color c1, Color c2) {
            Color result = new Color(0.0, 0.0, 0.0);

            result.red = c1.red - c2.red;
            result.green = c1.green - c2.green;
            result.blue = c1.blue - c2.blue;

            return result;
        }

        public static Color multiply(Color c1, Color c2) {
            Color result = new Color(0.0, 0.0, 0.0);

            result.red = c1.red * c2.red;
            result.green = c1.green * c2.green;
            result.blue = c1.blue * c2.blue;

            return result;
        }

        public static Color multiplyScalar(Color c1, double f) {
            Color result = new Color(0.0, 0.0, 0.0);

            result.red = c1.red * f;
            result.green = c1.green * f;
            result.blue = c1.blue * f;

            return result;
        }

        public Color divideFactor(Color c1, double f) {
            Color result = new Color(0.0, 0.0, 0.0);

            result.red = c1.red / f;
            result.green = c1.green / f;
            result.blue = c1.blue / f;

            return result;
        }

        public void limit() {
            this.red = this.red > 0.0 ? (this.red > 1.0 ? 1.0 : this.red) : 0.0;
            this.green = this.green > 0.0 ? (this.green > 1.0 ? 1.0 : this.green) : 0.0;
            this.blue = this.blue > 0.0 ? (this.blue > 1.0 ? 1.0 : this.blue) : 0.0;
        }

        public double distance(Color color) {
            double d = Math.abs(this.red - color.red) +
                    Math.abs(this.green - color.green) +
                    Math.abs(this.blue - color.blue);
            return d;
        }

        public static Color blend(Color c1, Color c2, double w) {
            Color result = new Color(0.0, 0.0, 0.0);
            result = Color.add(
                    Color.multiplyScalar(c1, 1 - w),
                    Color.multiplyScalar(c2, w));
            return result;
        }

        public int brightness() {
            Number r = Math.floor(this.red * MAX_COLOR_VALUE);
            Number g = Math.floor(this.green * MAX_COLOR_VALUE);
            Number b = Math.floor(this.blue * MAX_COLOR_VALUE);
            return ((r.intValue() * WEIGHT_RED + g.intValue() * WEIGHT_GREEN
                    + b.intValue() * WEIGHT_BLUE) >> (int) NUMBER_EIGHT);
        }

        public String toString() {
            double r = Math.floor(this.red * MAX_COLOR_VALUE);
            double g = Math.floor(this.green * MAX_COLOR_VALUE);
            double b = Math.floor(this.blue * MAX_COLOR_VALUE);
            return "rgb(" + r + "," + g + "," + b + ")";
        }

    }

    // Defined class Light
    public static class Light {
        public Vector position;
        public Color color;
        public double intensity = NUMBER_TEN;

        Light(Vector pos, Color c, double i) {
            this.position = pos;
            this.color = c;
            this.intensity = i;
        }

        public String toString() {
            return "Light [" +
                    this.position.x +
                    "," +
                    this.position.y +
                    "," +
                    this.position.z +
                    "]";
        }
    }

    // Defined class Vector
    public static class Vector {
        public double x = 0.0;
        public double y = 0.0;
        public double z = 0.0;

        Vector(Double xF, Double yF, Double zF) {
            this.x = (xF != null) ? xF : 0;
            this.y = (yF != null) ? yF : 0;
            this.z = (zF != null) ? zF : 0;
        }

        public void copy(Vector vector) {
            this.x = vector.x;
            this.y = vector.y;
            this.z = vector.z;
        }

        public Vector normalize() {
            double m = this.magnitude();
            return new Vector(this.x / m, this.y / m, this.z / m);
        }

        public double magnitude() {
            return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
        }

        public Vector cross(Vector w) {
            return new Vector(
                    -this.z * w.y + this.y * w.z,
                    this.z * w.x - this.x * w.z,
                    -this.y * w.x + this.x * w.y);
        }

        public double dot(Vector w) {
            return this.x * w.x + this.y * w.y + this.z * w.z;
        }

        public static Vector add(Vector v, Vector w) {
            return new Vector(w.x + v.x, w.y + v.y, w.z + v.z);
        }

        public static Vector subtract(Vector v, Vector w) {
            if (w == null || v == null) {
                System.out.println("Vectors must be defined");
            }
            return new Vector(v.x - w.x, v.y - w.y, v.z - w.z);
        }

        public static Vector multiplyVector(Vector v, Vector w) {
            return new Vector(v.x * w.x, v.y * w.y, v.z * w.z);
        }

        public static Vector multiplyScalar(Vector v, double w) {
            return new Vector(v.x * w, v.y * w, v.z * w);
        }

        public String toString() {
            return "Vector [" + this.x + "," + this.y + "," + this.z + "]";
        }
    }

    // Defined class Ray
    public static class Ray {
        public Vector position;
        public Vector direction;

        Ray(Vector pos, Vector dir) {
            this.position = pos;
            this.direction = dir;
        }

        public String toString() {
            return "Ray [" + position + "," + direction + "]";
        }
    }

    // Defined class Scene
    public static class Scene {
        public Camera camera = new Camera(
                new Vector(0.0, 0.0, -NUMBER_FIVE),
                new Vector(0.0, 0.0, 1.0),
                new Vector(0.0, 1.0, 0.0));
        public List<BaseShape> shapes = new ArrayList<>();
        public List<Light> lights = new ArrayList<>();
        public Background background = new Background(
                new Color(0.0, 0.0, NUMBER_POINT_FIVE),
                NUMBER_POINT_TWO);
    }

    // Defined class BaseMaterial
    public static class BaseMaterial {
        public double gloss = NUMBER_TWO;
        public double transparency = 0.0;
        public double reflection = 0.0;
        public double refraction = NUMBER_POINT_FIVE;
        public boolean hasTexture = false;

        public Color getColor(double u, double v) {
            return new Color(0.0, 0.0, 0.0);
        }

        public double wrapUp(double t) {
            t = t % NUMBER_TWO;
            if (t < -1) {
                t += NUMBER_TWO;
            }
            if (t >= 1) {
                t -= NUMBER_TWO;
            }
            return t;
        }

        public String toString() {
            return "Material [gloss=" +
                    this.gloss +
                    ", transparency=" +
                    this.transparency +
                    ", hasTexture=" +
                    this.hasTexture +
                    "]";
        }
    }

    // Defined class Solid
    public static class Solid extends BaseMaterial {
        public Color color;

        Solid(Color c, double refle, double refra, double tra, double g) {
            super();
            this.color = c;
            this.reflection = refle;
            this.transparency = tra;
            this.gloss = g;
            this.hasTexture = false;
        }

        public Color getColor(double u, double v) {
            return this.color;
        }

        public String toString() {
            return "SolidMaterial [gloss=" +
                    this.gloss +
                    ", transparency=" +
                    this.transparency +
                    ", hasTexture=" +
                    this.hasTexture +
                    "]";
        }
    }

    // Defined class Chessboard
    public static class Chessboard extends BaseMaterial {
        public Color colorEven;
        public Color colorOdd;
        public double density = NUMBER_POINT_FIVE;

        public Chessboard(Color cEven, Color cOdd, double refle, double tra, double g, double den) {
            super();
            this.colorEven = cEven;
            this.colorOdd = cOdd;
            this.reflection = refle;
            this.transparency = tra;
            this.gloss = g;
            this.density = den;
            this.hasTexture = true;
        }

        public Color getColor(double u, double v) {
            double t = wrapUp(u * this.density) * wrapUp(v * this.density);

            if (t < 0.0) {
                return colorEven;
            } else {
                return colorOdd;
            }
        }

        public String toString() {
            return "ChessMaterial [gloss=" +
                    gloss +
                    ", transparency=" +
                    transparency +
                    ", hasTexture=" +
                    hasTexture +
                    "]";
        }
    }

    // Defined class Base shape
    public static class BaseShape {
        public Vector position = new Vector(0.0, 0.0, 0.0);
        public double radius = 0.0;
        public BaseMaterial material = new BaseMaterial();

        public IntersectionInfo intersect(Ray ray) {
            return null;
        }
    }

    // Defined class Sphere
    public static class Sphere extends BaseShape {
        Sphere(Vector pos, double rad, BaseMaterial mater) {
            super();
            this.radius = rad;
            this.position = pos;
            this.material = mater;
        }

        public IntersectionInfo intersect(Ray ray) {
            IntersectionInfo info = new IntersectionInfo();
            info.shape = this;
            Vector dst = Vector.subtract(ray.position, this.position);
            double B = dst.dot(ray.direction);
            double C = dst.dot(dst) - this.radius * this.radius;
            double D = B * B - C;
            if (D > 0) {
                info.isHit = true;
                info.distance = -B - Math.sqrt(D);
                info.position = Vector.add(
                        ray.position,
                        Vector.multiplyScalar(ray.direction, info.distance));
                info.normal = Vector.subtract(info.position, this.position).normalize();
                info.color = this.material.getColor(0, 0);
            } else {
                info.isHit = false;
            }
            return info;
        }

        public String toString() {
            return "Sphere [position=" + this.position + ", radius=" + this.radius + "]";
        }
    }

    // Defined class Plane
    public static class Plane extends BaseShape {
        public double d = 0.0;

        Plane(Vector pos, double dF, BaseMaterial mater) {
            super();
            this.position = pos;
            this.d = dF;
            this.material = mater;
        }

        public IntersectionInfo intersect(Ray ray) {
            IntersectionInfo info = new IntersectionInfo();
            double vD = this.position.dot(ray.direction);
            if (vD == 0) {
                return info;
            }
            double t = -(this.position.dot(ray.position) + this.d) / vD;
            if (t <= 0) {
                return info;
            }
            info.shape = this;
            info.isHit = true;

            info.position = Vector.add(
                    ray.position,
                    Vector.multiplyScalar(ray.direction, t));
            info.normal = this.position;
            info.distance = t;

            if (this.material.hasTexture) {
                Vector vU = new Vector(this.position.y, this.position.z, -this.position.x);
                Vector vV = vU.cross(this.position);
                double u = info.position.dot(vU);
                double v = info.position.dot(vV);
                info.color = this.material.getColor(u, v);
            } else {
                info.color = this.material.getColor(0, 0);
            }
            return info;
        }

        public String toString() {
            return "Plane [" + this.position + ", d=" + this.d + "]";
        }
    }

    // Defined class IntersectionInfo
    public static class IntersectionInfo {
        public boolean isHit = false;
        public int hitCount = 0;
        public BaseShape shape = new BaseShape();
        public Vector position = new Vector(0.0, 0.0, 0.0);
        public Vector normal = new Vector(0.0, 0.0, 0.0);
        public Color color = new Color(0.0, 0.0, 0.0);
        public double distance = 0;

        public String toString() {
            return "Intersection [" + position + "]";
        }
    }

    // Defined class Camera
    public static class Camera {
        public Vector position;
        public Vector lookAt;
        public Vector equator;
        public Vector up;
        public Vector screne;

        Camera(Vector pos, Vector lookV, Vector upV) {
            this.position = pos;
            this.lookAt = lookV;
            this.up = upV;
            this.equator = this.lookAt.normalize().cross(this.up);
            this.screne = Vector.add(this.position, this.lookAt);
        }

        public Ray getRay(double vx, double vy) {
            Vector pos = Vector.subtract(
                    this.screne,
                    Vector.subtract(
                            Vector.multiplyScalar(this.equator, vx),
                            Vector.multiplyScalar(this.up, vy)));
            pos.y *= -1;
            Vector dir = Vector.subtract(pos, this.position);
            Ray ray = new Ray(pos, dir.normalize());
            return ray;
        }

        public String toString() {
            return "Ray []";
        }
    }

    // Defined class Background
    public static class Background {
        public Color color;
        public double ambience = 0.0;

        Background(Color c, double amb) {
            this.color = c;
            this.ambience = amb;
        }
    }

    // Defined class Engine
    public static class Engine {
        public Options options;

        Engine(Options opt) {
            this.options = opt;
            this.options.canvasHeight /= (int) this.options.pixelHeight;
            this.options.canvasWidth /= (int) this.options.pixelWidth;
        }

        public void setPixel(int x, int y, Color color) {
            if (x == y) {
                checkNumber += color.brightness();
            }
        }

        public void renderScene(Scene scene) {
            checkNumber = 0;

            int canvasHeight = this.options.canvasHeight;
            int canvasWidth = this.options.canvasWidth;

            for (int y = 0; y < canvasHeight; y++) {
                for (int x = 0; x < canvasWidth; x++) {
                    double yp = ((y * 1.0) / canvasHeight) * NUMBER_TWO - 1;
                    double xp = ((x * 1.0) / canvasWidth) * NUMBER_TWO - 1;

                    Ray ray = scene.camera.getRay(xp, yp);

                    Color color = this.getPixelColor(ray, scene);

                    this.setPixel(x, y, color);

                    // debugLog("y = " + y + ", x = " + x + ", ray.position = "
                    //         + (ray.position != null ? ray.position.toString() : "null"));
                    // debugLog("y = " + y + ", x = " + x + ", ray.direction = "
                    //         + (ray.direction != null ? ray.direction.toString() : "null"));
                    // debugLog("y = " + y + ", x = " + x + ", color = " + color.toString());
                }
            }
            // debugLog("checkNumber = " + checkNumber);
            if (checkNumber != CHECK_NUMBER) {
                System.out.println("Scene rendered incorrectly");
            }
        }

        public Color getPixelColor(Ray ray, Scene scene) {
            IntersectionInfo info = testIntersection(ray, scene, null);
            if (info.isHit) {
                Color color = this.raytrace(info, ray, scene, 0);
                return color;
            }
            return scene.background.color;
        }

        public IntersectionInfo testIntersection(Ray ray, Scene scene, BaseShape exclude) {
            int hits = 0;
            IntersectionInfo best = new IntersectionInfo();
            best.distance = MAX_DISTANCE;

            for (int i = 0; i < scene.shapes.size(); i++) {
                BaseShape shape = scene.shapes.get(i);

                if (shape != exclude) {
                    IntersectionInfo info = shape.intersect(ray);
                    if (info != null &&
                            info.isHit &&
                            info.distance >= 0 &&
                            info.distance < best.distance) {
                        best = info;
                        hits++;
                    }
                }
            }
            best.hitCount = hits;
            return best;
        }

        public Ray getReflectionRay(Vector p, Vector n, Vector v) {
            double c1 = -n.dot(v);
            Vector R1 = Vector.add(Vector.multiplyScalar(n, NUMBER_TWO * c1), v);
            return new Ray(p, R1);
        }

        public Color raytrace(
                IntersectionInfo info,
                Ray ray,
                Scene scene,
                int depth) {
            // Calc ambient
            Color color = Color.multiplyScalar(info.color, scene.background.ambience);
            Color oldColor = color;
            double shininess = Math.pow(10, info.shape.material.gloss + 1);

            for (int i = 0; i < scene.lights.size(); i++) {
                Light light = scene.lights.get(i);

                // Calc diffuse lighting
                Vector v = Vector.subtract(light.position, info.position).normalize();
                if (this.options.renderDiffuse) {
                    double L = v.dot(info.normal);
                    if (L > 0.0) {
                        color = Color.add(
                                color,
                                Color.multiply(info.color, Color.multiplyScalar(light.color, L)));
                    }
                }
                color = this.getColorByDepth(info, ray, scene, depth, color);
                // Render shadows and highlights
                IntersectionInfo shadowInfo = new IntersectionInfo();
                color = this.renderShadowsAndHighlights(
                        info,
                        scene,
                        shadowInfo,
                        color,
                        v,
                        light,
                        shininess);
            }
            color.limit();
            return color;
        }

        public Color getColorByDepth(
                IntersectionInfo info,
                Ray ray,
                Scene scene,
                int depth,
                Color color) {
            // The greater the depth the more accurate the colours, but
            // this is exponentially (!) expensive
            if (depth <= options.rayDepth) {
                // calculate reflection ray
                if (this.options.renderReflections && info.shape != null &&
                        info.shape.material.reflection > 0) {
                    Ray reflectionRay = getReflectionRay(
                            info.position,
                            info.normal,
                            ray.direction);
                    IntersectionInfo refl = testIntersection(reflectionRay, scene, info.shape);

                    if (refl.isHit && refl.distance > 0) {
                        refl.color = this.raytrace(refl, reflectionRay, scene, depth + 1);
                    } else {
                        refl.color = scene.background.color;
                    }
                    color = Color.blend(
                            color,
                            refl.color,
                            info.shape.material.reflection);
                }
            }
            return color;
        }

        public Color renderShadowsAndHighlights(
                IntersectionInfo info,
                Scene scene,
                IntersectionInfo shadowInfo,
                Color color,
                Vector v,
                Light light,
                double shininess) {
            if (this.options.renderShadows) {
                Ray shadowRay = new Ray(info.position, v);
                shadowInfo = this.testIntersection(shadowRay, scene, info.shape);
                if (shadowInfo.isHit && shadowInfo.shape != info.shape) {
                    Color vA = Color.multiplyScalar(color, NUMBER_POINT_FIVE);
                    double dB = NUMBER_POINT_FIVE *
                            Math.pow(
                                    shadowInfo.shape.material.transparency,
                                    NUMBER_POINT_FIVE);
                    color = Color.addScalar(vA, dB);
                }
            }
            // Phong specular highlights
            if (this.options.renderHighlights &&
                    !shadowInfo.isHit &&
                    info.shape.material.gloss > 0) {
                Vector lV = Vector.subtract(info.shape.position, light.position).normalize();
                Vector eVector = Vector.subtract(
                        scene.camera.position,
                        info.shape.position).normalize();
                Vector hVector = Vector.subtract(eVector, lV).normalize();
                double glossWeight = Math.pow(
                        Math.max(info.normal.dot(hVector), 0.0),
                        shininess);
                color = Color.add(Color.multiplyScalar(light.color, glossWeight), color);
            }
            return color;
        }
    }

    // Defined class Options
    public static class Options {
        public int canvasHeight = DEFAULT_CANVAS_HEIGHT;
        public int canvasWidth = DEFAULT_CANVAS_WIDTH;
        public double pixelWidth = NUMBER_TWO;
        public double pixelHeight = NUMBER_TWO;
        public boolean renderDiffuse = false;
        public boolean renderShadows = false;
        public boolean renderHighlights = false;
        public boolean renderReflections = false;
        public double rayDepth = NUMBER_TWO;
    }

    public static Scene initShapes(Scene scene) {
        Sphere sphere = new Sphere(
                new Vector(-NUMBER_ONE_POINT_FIVE, NUMBER_ONE_POINT_FIVE, NUMBER_TWO),
                NUMBER_ONE_POINT_FIVE,
                new Solid(
                        new Color(0.0, NUMBER_POINT_FIVE, NUMBER_POINT_FIVE),
                        NUMBER_POINT_THREE,
                        0.0,
                        0.0,
                        NUMBER_TWO));

        Sphere sphere1 = new Sphere(
                new Vector(1.0, NUMBER_POINT_TWO_FIVE, 1.0),
                NUMBER_POINT_FIVE,
                new Solid(
                        new Color(NUMBER_POINT_NINE, NUMBER_POINT_NINE, NUMBER_POINT_NINE),
                        NUMBER_POINT_ONE,
                        0.0,
                        0.0,
                        NUMBER_ONE_POINT_FIVE));

        Plane plane = new Plane(
                new Vector(
                        NUMBER_POINT_ONE,
                        NUMBER_POINT_NINE,
                        -NUMBER_POINT_FIVE).normalize(),
                NUMBER_ONE_POINT_TWO,
                new Chessboard(
                        new Color(1.0, 1.0, 1.0),
                        new Color(0.0, 0.0, 0.0),
                        NUMBER_POINT_TWO,
                        0.0,
                        1,
                        NUMBER_POINT_SEVEN));

        scene.shapes.add(sphere);
        scene.shapes.add(sphere1);
        scene.shapes.add(plane);

        return scene;
    }

    public static Scene initLights(Scene scene) {
        Light light = new Light(
                new Vector(NUMBER_FIVE, NUMBER_TEN, -1.0),
                new Color(NUMBER_POINT_EIGHT, NUMBER_POINT_EIGHT, NUMBER_POINT_EIGHT),
                0);
        Light light1 = new Light(
                new Vector(-NUMBER_THREE, NUMBER_FIVE, DEFAULT_VECTOR_Z),
                new Color(NUMBER_POINT_EIGHT, NUMBER_POINT_EIGHT, NUMBER_POINT_EIGHT),
                NUMBER_ONE_HUNDRED);

        scene.lights.add(light);
        scene.lights.add(light1);

        return scene;
    }

    public static void renderScene() {
        Scene scene = new Scene();

        scene.camera = new Camera(
                new Vector(0.0, 0.0, DEFAULT_VECTOR_Z),
                new Vector(-NUMBER_POINT_TWO, 0.0, NUMBER_FIVE),
                new Vector(0.0, 1.0, 0.0));
        scene.background = new Background(
                new Color(NUMBER_POINT_FIVE, NUMBER_POINT_FIVE, NUMBER_POINT_FIVE),
                NUMBER_POINT_FOUR);
        scene = initShapes(scene);
        scene = initLights(scene);

        Options option = new Options();
        option.canvasWidth = DEFAULT_CANVAS_WIDTH;
        option.canvasHeight = DEFAULT_CANVAS_HEIGHT;
        option.pixelWidth = NUMBER_FIVE;
        option.pixelHeight = NUMBER_FIVE;
        option.renderDiffuse = true;
        option.renderHighlights = true;
        option.renderShadows = true;
        option.renderReflections = true;
        option.rayDepth = NUMBER_TWO;

        Engine raytracer = new Engine(option);
        raytracer.renderScene(scene);

        // debugLog("Create camera: ");
        // debugLog(scene.camera.toString());
        // debugLog("Create Background： ");
        // debugLog(scene.background.toString());
        // debugLog("Create sphere: ");
        // debugLog(sphere.toString());
        // debugLog("Create sphere1: ");
        // debugLog(sphere1.toString());
        // debugLog("Create plane: ");
        // debugLog(plane.toString());
        // debugLog("Create light: ");
        // debugLog(light.toString());
        // debugLog("Create light1: ");
        // debugLog(light1.toString());
        // debugLog("Create option ");
        // debugLog(String.valueOf(option));
    }

}

/*
 * @State
 * 
 * @Tags Jet stream2
 */
class Benchmark {
    /*
     * @Benchmark
     */
    public static void runIteration() {
        for (int i = 0; i < (int) Raytrace.NUMBER_FIVE; i++) {
            Raytrace.renderScene();
        }
    }

    public static void run() {
        long startTime = System.nanoTime();
        for (int index = 0; index < Raytrace.MAX_RUN_TIMES; index++) {
            Benchmark.runIteration();
        }
        long endTime = System.nanoTime();
        double timeElapsed = (double)(endTime - startTime) / 1000000;
        System.out.println("raytrace: ms = " + timeElapsed);
    }

    public static void main(String[] args) {
        Benchmark.run();
    }
}
